﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using DShowNET;

namespace DFControlLibrary
{
    /// <summary>
    /// カメラ制御クラス
    /// </summary>
    class DXCaptureVideoFile : ISampleGrabberCB
    {
        private bool active = false;		//初期化正常完了を示すフラグ
		private bool capturedFlag = true;	//キャプチャの終了を示すフラグ
		private bool updatedFlag = false;   //ビットマップの更新を示すフラグ
        private Image capturedImage = null;


		private IGraphBuilder graphBuilder; //基本的なフィルタグラフマネージャ

		private IMediaControl mediaControl;//データのストリーミングの移動、ポーズ、停止などの処理用のインタフェース．
		private IMediaEventEx mediaEvent; //DirectShowイベント処理用のインタフェース
        private IVideoWindow videoWindow;
		
		private ISampleGrabber sampleGrabber; //フィルタグラフ内を通る個々のデータ取得用のインタフェース． 
		private IBaseFilter grabFilter; //Grabber Filterのインタフェース．

		private VideoInfoHeader videoInfoHeader = null;//ビデオイメージのフォーマットを記述する構造体
		

		private byte[] frameArray;//キャプチャしたフレームデータ用の配列	

        private ulong FrameNumber = 0;

        private string videoFile = string.Empty;

        /// <summary>
        /// The height of the video capture image
        /// </summary>
        public int CaptureHeight
        {
            get
            {
                if (videoInfoHeader == null)
                    return 240;
                else
                    return videoInfoHeader.BmiHeader.Height;
            }
        }

        /// <summary>
        /// The width of the video capture image
        /// </summary>
        public int CaptureWidth
        {
            get
            {
                if (videoInfoHeader == null)
                    return 320;
                else
                    return videoInfoHeader.BmiHeader.Width; }
        }

		public DXCaptureVideoFile(string path)
		{
			this.active = false;
            this.videoFile = path;

			//DirectX ver 8.1以上が正常にインストールされているか？
			if(! DsUtils.IsCorrectDirectXVersion())
			{
				//MessageBox.Show("アプリケーションの動作にはDirectX ver 8.1以上が必要です．" );
				return;
			}
        }

        ~DXCaptureVideoFile()
		{			
			CloseInterfaces();
		}

		//DicrectShow関連の初期化
		public bool InitVideo()
		{
			int result;

			try
            {
				//各種COM コンポーネントを呼び出し，インタフェースを取得する．
				if(!GetInterfaces()) 
					return false;
				
				//画像のプレビューや静止画キャプチャ用の設定を行う．
				if(!SetupGraph())	
					return false;

				//プレビューを開始する．
				result = mediaControl.Run();

				if(result < 0)
					Marshal.ThrowExceptionForHR(result);

                //初期設定は成功．
                this.active = true;

				return true;
			}
			catch//(Exception e)
			{
				//MessageBox.Show( "ビデオストリームを正常に読み込めません．\r\n" + e.ToString());	
				return false;
			}
		}		

		//各種のCOM コンポーネントを作成し，インタフェースを取得する．
		bool GetInterfaces()
		{
			Type comType = null;
			object comObject = null;

			try
			{	
				//graphBuilderを作成．
				comType = Type.GetTypeFromCLSID(Clsid.FilterGraph);
				if(comType == null)
					throw new NotImplementedException("DirectShowのFiterGraphオブジェクトが作成できません．");
				comObject = Activator.CreateInstance(comType);
				graphBuilder = (IGraphBuilder) comObject;
				comObject = null;

				//サンプルグラバ(sampleGrabber)を作成
				comType = Type.GetTypeFromCLSID(Clsid.SampleGrabber);
				if(comType == null)
					throw new NotImplementedException("DirectShowのSampleGrabberオブジェクトが作成できません．");
				comObject = Activator.CreateInstance(comType);
				sampleGrabber = (ISampleGrabber) comObject;
				comObject = null;                

				//各種インタフェースを取得．
                videoWindow = (IVideoWindow)graphBuilder;
				mediaControl = (IMediaControl) graphBuilder;
				mediaEvent = (IMediaEventEx) graphBuilder;

                //フィルタと関連付ける．
				grabFilter = (IBaseFilter) sampleGrabber;

				return true;
			}
		
			catch //(Exception e)
			{
				//MessageBox.Show("インタフェースの取得に失敗しました．\r\n" + e.ToString());
				return false;	
			}

			finally
			{
				//COMリソースを解放
				if(comObject !=null)
				{
					Marshal.ReleaseComObject(comObject);
					comObject = null;
				}
			}
		}

		//画像のプレビューや静止画キャプチャ用の設定を行う．
		bool SetupGraph()
		{
			int result;
		
			try
			{ 
				//キャプチャするビデオデータのフォーマットを設定．
				AMMediaType amMediaType = new AMMediaType();
				amMediaType.majorType = MediaType.Video;
				amMediaType.subType = MediaSubType.RGB24;
				amMediaType.formatType = FormatType.VideoInfo; 
				
				result = sampleGrabber.SetMediaType(amMediaType);
				if(result < 0) Marshal.ThrowExceptionForHR(result);
                
				
				//grabFilter(変換フィルタ)をgraphBuilder（フィルタグラフマネージャ）に追加．
				result = graphBuilder.AddFilter(grabFilter, "Frame Grab Filter");
				if(result < 0) Marshal.ThrowExceptionForHR(result);

                //グラフを生成
                mediaControl.RenderFile(videoFile);

                //フレームキャプチャの設定が完了したかを確認する．
                amMediaType = new AMMediaType();
                result = sampleGrabber.GetConnectedMediaType(amMediaType);
                if (result < 0) Marshal.ThrowExceptionForHR(result);
                if ((amMediaType.formatType != FormatType.VideoInfo) || (amMediaType.formatPtr == IntPtr.Zero))
                    throw new NotSupportedException("キャプチャ(Grab)できないメディアフォーマットです．");

				//キャプチャするビデオデータのフォーマットから，videoInfoHeaderを作成する．
				videoInfoHeader = 
					(VideoInfoHeader) Marshal.PtrToStructure(amMediaType.formatPtr, typeof(VideoInfoHeader));
				Marshal.FreeCoTaskMem(amMediaType.formatPtr);
				amMediaType.formatPtr = IntPtr.Zero;
			
				//フィルタ内を通るサンプルをバッファにコピーしないように指定する．
				result = sampleGrabber.SetBufferSamples(false);
				//サンプルを一つ（1フレーム）受け取ったらフィルタを停止するように指定する．
				if(result == 0) result = sampleGrabber.SetOneShot(false);
				//コールバック関数の利用を停止する．
				if(result == 0) result = sampleGrabber.SetCallback(this, 1);

				if(result < 0) Marshal.ThrowExceptionForHR(result);

                //ダミーパネルの作成
                System.Windows.Forms.Panel dummyPanel = new System.Windows.Forms.Panel();

                //ダミーパネルの中に表示
                result = videoWindow.put_Owner(dummyPanel.Handle);

				return true;
			}
			catch//(Exception e)
			{
				//MessageBox.Show("フィルターグラフの設定に失敗しました．" + e.ToString());
				return false;
			}
			
		}
            
		//静止画のキャプチャを開始する．
		public bool StartGrabber()
		{
			//int result;
			if(sampleGrabber == null) return false;

			if(frameArray == null)
			{
				//キャプチャするフレームデータのサイズを設定する．
				int size = videoInfoHeader.BmiHeader.ImageSize;		
				if ((size < 1000) || (size > 16000000)) return false; //サイズが適正か？	
				frameArray = new byte [size + 512];
			}

			capturedFlag = false;

            //try	
            //{
				
            //    //ビデオデータのサンプリングに利用するコールバック メソッドを指定する．
            //        //第一引数	 ISampleGrabberCB インターフェイスへのポインタ
            //        //			 nullを指定するとコールバックを解除
            //        //第二引数	0->ISampleGrabberCB.SampleCB メソッドを利用 	
            //        //			1->ISampleGrabberCB.BufferCB メソッドを利用
            //    //result = sampleGrabber.SetCallback( this, 1 );
            //}
            //catch//(Exception e)
            //{
            //    //MessageBox.Show(e.ToString());
            //}
			return true;
		}

        public bool StopGrabber()
        {
            int result;
            if (sampleGrabber == null) return false;
            try
            {

                //ビデオデータのサンプリングに利用するコールバック メソッドを指定する．
                //第一引数	 ISampleGrabberCB インターフェイスへのポインタ
                //			 nullを指定するとコールバックを解除
                //第二引数	0->ISampleGrabberCB.SampleCB メソッドを利用 	
                //			1->ISampleGrabberCB.BufferCB メソッドを利用
                result = sampleGrabber.SetCallback(null, 0);
            }
            catch //(Exception e)
            {
                //MessageBox.Show(e.ToString());
                return false;
            }
            return true;
        }

		//フレームキャプチャ完了時に呼び出されるコールバック関数
		int ISampleGrabberCB.SampleCB(double SampleTime, IMediaSample pSample)
		{
			return 0;
		}
	
		//フレームキャプチャ完了時に呼び出されるコールバック関数
		int ISampleGrabberCB.BufferCB(double SampleTime, IntPtr pBuffer, int BufferLength)
		{
            FrameNumber++;

			//既にコールバックが呼び出されているか，frameArrayが初期化されていなければ終了．
			if(capturedFlag || (frameArray == null)) 
			{
				return 0;
			}
			
			capturedFlag = true;
			//メモリ内のサンプリングされたデータ(pBuffer)を配列(frameArray)にコピーする．
			if((pBuffer != IntPtr.Zero) && (BufferLength > 1000) && (BufferLength <= frameArray.Length))
				Marshal.Copy(pBuffer, frameArray, 0, BufferLength);
			
			try
			{					
                OnCaptureDone();                
			}
			catch//(Exception e)
			{
				//MessageBox.Show(e.ToString());
			}
			return 0;
		}

		//キャプチャしたデータの配列をメモリ空間内に固定し，ビットマップに変換する． 
		void OnCaptureDone()
		{
			try
			{
				//フレームデータのサイズを取得
				int width = videoInfoHeader.BmiHeader.Width;
				int height = videoInfoHeader.BmiHeader.Height;

				//widthが4の倍数でない場合＆widthとheightの値が適正でない場合は終了．
				if( ((width & 0x03) != 0) || (width < 32) || (width > 4096) || (height < 32) || (height> 4096) )
					return;

				//stride(1ライン分のデータサイズ(byte)=width* 3(RGB))を設定．
				int stride = width * 3;

				//配列frameArrayのアドレスを，メモリ空間内で固定する．
				GCHandle gcHandle = GCHandle.Alloc(frameArray, GCHandleType.Pinned);
				
				int addr = (int) gcHandle.AddrOfPinnedObject();
				addr += (height - 1) * stride;
				
				//frameArrayを格納したメモリアドレスから，ビットマップデータを作成．
                capturedImage= new Bitmap(width, height, -stride, PixelFormat.Format24bppRgb, (IntPtr)addr);
				gcHandle.Free();
				//frameArray = null;               

				//更新完了．
				updatedFlag = true;

				return;
			}
			catch//(Exception e)
			{
				//MessageBox.Show("フレームのキャプチャ（Grab）に失敗しました．" + e.ToString());
			}
		}

		//COMやインタフェースの解放を行う
		public void CloseInterfaces()
		{
			try
			{
                capturedFlag = true;
				if(mediaControl != null)
				{
					mediaControl.Stop();
					Thread.Sleep(100);
					mediaControl = null;
				}

				if(grabFilter != null) grabFilter = null;

				if(sampleGrabber != null)
				{
					Marshal.ReleaseComObject(sampleGrabber);
					sampleGrabber = null;
				}                

				if(graphBuilder != null)
				{
					Marshal.ReleaseComObject(graphBuilder);
					graphBuilder = null;
				}				
			}
			catch//(Exception e)
			{
				//MessageBox.Show("インタフェースの終了に失敗しました．"+ e.ToString());
			}
		}

		//初期化が正常に行われたか？
		public bool IsActive
		{
			get {return this.active;}
		}

		//静止画のキャプチャが終了＆更新したか？
		public bool IsUpdated
		{
			get{return this.updatedFlag;}
			set{this.updatedFlag = value;}
		}

        //キャプチャした画像
        public Image CapturedImage
        {
            get { return capturedImage; }
        }
    }
}
